[Android] Volleyライブラリでハマったこと
volleyでハマったこと
- HTTPステータスコード 401 が返却されているはずなのに、ステータスコードが取得できない! (正確には -1が返却されてる)
その時は以下の警告が表示された。
java.io.IOException:No authentication challenges found
原因を探る
どこでExceptionが出ているのか原因を探ってみました。
- まずはVolleyのRequestQueue内を探ってみました。
Volley#newRequestQueue()
public static RequestQueue newRequestQueue(Context context, HttpStack stack) { File cacheDir = new File(context.getCacheDir(), DEFAULT_CACHE_DIR); String userAgent = "volley/0"; try { String packageName = context.getPackageName(); PackageInfo info = context.getPackageManager().getPackageInfo(packageName, 0); userAgent = packageName + "/" + info.versionCode; } catch (NameNotFoundException e) { } if (stack == null) { if (Build.VERSION.SDK_INT >= 9) { stack = new HurlStack(); } else { // Prior to Gingerbread, HttpUrlConnection was unreliable. // See: http://android-developers.blogspot.com/2011/09/androids-http-clients.html stack = new HttpClientStack(AndroidHttpClient.newInstance(userAgent)); } } Network network = new BasicNetwork(stack); RequestQueue queue = new RequestQueue(new DiskBasedCache(cacheDir), network); queue.start(); return queue; }
newRequestQueueの内部では、Androidのバージョンによって使用するNetwork通信ライブラリを変更しているようです。
検証していた機種は Android 4.0系だったので HurlStack()を使用していました。
HurlStack()内部を探ってみました。
HurlStack#performRequest()
@Override public HttpResponse performRequest(Request<?> request, Map<String, String> additionalHeaders) throws IOException, AuthFailureError { String url = request.getUrl(); HashMap<String, String> map = new HashMap<String, String>(); map.putAll(request.getHeaders()); map.putAll(additionalHeaders); if (mUrlRewriter != null) { String rewritten = mUrlRewriter.rewriteUrl(url); if (rewritten == null) { throw new IOException("URL blocked by rewriter: " + url); } url = rewritten; } URL parsedUrl = new URL(url); HttpURLConnection connection = openConnection(parsedUrl, request); for (String headerName : map.keySet()) { connection.addRequestProperty(headerName, map.get(headerName)); } setConnectionParametersForRequest(connection, request); // Initialize HttpResponse with data from the HttpURLConnection. ProtocolVersion protocolVersion = new ProtocolVersion("HTTP", 1, 1); int responseCode = connection.getResponseCode(); if (responseCode == -1) { // -1 is returned by getResponseCode() if the response code could not be retrieved. // Signal to the caller that something was wrong with the connection. throw new IOException("Could not retrieve response code from HttpUrlConnection."); } StatusLine responseStatus = new BasicStatusLine(protocolVersion, connection.getResponseCode(), connection.getResponseMessage()); BasicHttpResponse response = new BasicHttpResponse(responseStatus); response.setEntity(entityFromConnection(connection)); for (Entry<String, List<String>> header : connection.getHeaderFields().entrySet()) { if (header.getKey() != null) { Header h = new BasicHeader(header.getKey(), header.getValue().get(0)); response.addHeader(h); } } return response; }
デバッグしていくと、上記23行目のconnection.getResponseCode()からExceptionがthrowされてました。(27行目のExceptionではない。)
HttpURLConnectionでExceptionが出てたので、よくよく仕様を確認してみると…
HttpURLConnection.html#getResponseCode()
前述の応答からは整数 200 と 401 をそれぞれ取り出します。応答が識別できない (有効な HTTP でない) 場合は -1 を返します。
どうやらアプリ側は悪くない模様? 有効なHTTPでない って意味不。
調べた結果、RFC規格で401のステータスコードは WWW-Authenticateのヘッダーを返さないといけないらしいです。 ということで、サーバAPIの返却値のヘッダーに WWW-Authenticateを入れてもらったら無事に通信できました!
この辺のstackoverfrowにも書いてますね…
.oO(今回はサーバ側変更できたからいいけど、変更不可だったら無理矢理HTTPClient使うとかしないと詰む?)
アプリ開発者もサーバ側の知識が必要だな〜と感じました。というお話でした。